package org.gaming.prefab.thing;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.gaming.prefab.IGameCause;
import org.gaming.prefab.thing.cost.CostReceipt;
import org.gaming.prefab.thing.cost.ICostThing;
import org.gaming.prefab.thing.reward.IRewardThing;
import org.gaming.prefab.thing.reward.RewardReceipt;

public abstract class AbstractThingService<Num extends Number, Reward extends IRewardThing<Num>, Cost extends ICostThing<Num>, RReceipt extends RewardReceipt<Num>, CReceipt extends CostReceipt<Num>> {

	/**
	 * 加锁KEY前缀
	 */
	private static final String PREFIX = "THING";

	public abstract ThingStorer<Num, Reward, Cost, RReceipt, CReceipt> getStorer(IThingType thingType);
//	{
//		ThingStorer<?, ?, ?, ?, ?> thingStorer = ThingStorer.getStorer(thingType);
//		if(thingStorer == null) {
//			throw new RuntimeException(thingType + "没有找到该类型的储存器");
//		}
//		return thingStorer;
//	}

	
	public abstract IThingType thingTypeOf(IThing thing);
	
	/**
	 * 物品是否足够
	 */
	public final boolean isEnough(long entityId, Cost costThing) {
		IThingType thingType = thingTypeOf(costThing);
		ThingStorer<Num, Reward, Cost, RReceipt, CReceipt> thingStorer = getStorer(thingType);
		return thingStorer.isEnough(entityId, costThing);
	}

	/**
	 * 物品是否足够
	 */
	public final boolean isEnough(long entityId, List<Cost> costThings) {
		List<Cost> costs = mergeCosts(costThings);
		for(Cost costThing : costs) {
			if(!isEnough(entityId, costThing)) {
				return false;
			}
		}
		return true;
	}

	public final CostReceipt<Num> cost(long entityId, Cost costThing, IGameCause cause, String remark) {
		return cost(entityId, Collections.singletonList(costThing), cause, remark);
	}
	
	public final CostReceipt<Num> cost(long entityId, Cost costThing, IGameCause cause) {
		return cost(entityId, Collections.singletonList(costThing), cause, "");
	}

	public final CostReceipt<Num> cost(long entityId, List<Cost> costThings, IGameCause cause) {
		return cost(entityId, costThings, cause, "");
	}
	
	public final CostReceipt<Num> cost(long entityId, List<Cost> costThings, IGameCause cause, String remark) {
		List<Cost> costs = mergeCosts(costThings);
		Map<ThingStorer<Num, Reward, Cost, RReceipt, CReceipt>, List<Cost>> grouping = groupingThingByType(costs);
		
		CostReceipt<Num> costReceipt = null;
		synchronized (new String(PREFIX + entityId).intern()) {
			for (Entry<ThingStorer<Num, Reward, Cost, RReceipt, CReceipt>, List<Cost>> entry : grouping.entrySet()) {
				ThingStorer<Num, Reward, Cost, RReceipt, CReceipt> storer = entry.getKey();
				List<Cost> costing = entry.getValue();
				storer.prepareCost(entityId, costing, cause);
			}
			costReceipt = new CostReceipt<>();
			int dbZone = dbZone(entityId);
			
			for (Entry<ThingStorer<Num, Reward, Cost, RReceipt, CReceipt>, List<Cost>> entry : grouping.entrySet()) {
				ThingStorer<Num, Reward, Cost, RReceipt, CReceipt> storer = entry.getKey();
				List<Cost> costing = entry.getValue();
				
				CostReceipt<Num> partReceipt = storer.cost(entityId, dbZone, costing, cause, remark);
				costReceipt.merge(partReceipt);
			}
		}
		costFinished(entityId, costReceipt, remark);
		return costReceipt;
	}
	
	/**
	 * 尝试添加
	 * @param entityId
	 * @param rewardThings
	 * @param cause
	 * @param noticeType
	 * @param remark
	 * @return
	 */
	public final boolean tryAdd(long entityId, List<Reward> rewardThings, IGameCause cause) {
		List<Reward> rewards = this.mergeRewards(rewardThings);
		Map<ThingStorer<Num, Reward, Cost, RReceipt, CReceipt>, List<Reward>> grouping = groupingThingByType(rewards);
		
		boolean success = true;
		synchronized (new String(PREFIX + entityId).intern()) {
			for (Entry<ThingStorer<Num, Reward, Cost, RReceipt, CReceipt>, List<Reward>> entry : grouping.entrySet()) {
				ThingStorer<Num, Reward, Cost, RReceipt, CReceipt> storer = entry.getKey();
				List<Reward> rewarding = entry.getValue();
				storer.prepareAdd(entityId, rewarding);
				success = storer.tryAdd(entityId, rewarding) && success;
				if(!success) {
					break;
				}
			}
		}
		return success;
	}
	
	/**
	 * 添加奖励
	 * @param entityId
	 * @param rewardThings
	 * @param cause
	 * @param noticeType
	 * @param remark
	 * @return
	 */
	public final RewardReceipt<Num> add(long entityId, List<Reward> rewardThings, IGameCause cause, NoticeType noticeType, String remark) {
		List<Reward> rewards = this.mergeRewards(rewardThings);
		Map<ThingStorer<Num, Reward, Cost, RReceipt, CReceipt>, List<Reward>> grouping = groupingThingByType(rewards);

		RewardReceipt<Num> rewardReceipt = null;
		synchronized (new String(PREFIX + entityId).intern()) {
			for (Entry<ThingStorer<Num, Reward, Cost, RReceipt, CReceipt>, List<Reward>> entry : grouping.entrySet()) {
				ThingStorer<Num, Reward, Cost, RReceipt, CReceipt> storer = entry.getKey();
				List<Reward> rewarding = entry.getValue();
				storer.prepareAdd(entityId, rewarding);
			}
			rewardReceipt = new RewardReceipt<>();
			int dbZone = dbZone(entityId);
			
			for (Entry<ThingStorer<Num, Reward, Cost, RReceipt, CReceipt>, List<Reward>> entry : grouping.entrySet()) {
				ThingStorer<Num, Reward, Cost, RReceipt, CReceipt> storer = entry.getKey();
				List<Reward> rewarding = entry.getValue();
				
				RewardReceipt<Num> partReceipt = storer.add(entityId, dbZone, rewarding, cause, remark);
				rewardReceipt.merge(partReceipt);
			}
		}
		addFinished(entityId, rewardReceipt, noticeType, remark);
		return rewardReceipt;
	}

	public final RewardReceipt<Num> add(long entityId, List<Reward> rewardThings, IGameCause cause) {
		return add(entityId, rewardThings, cause, NoticeType.NORMAL, "");
	}
	
	public final RewardReceipt<Num> add(long entityId, List<Reward> rewardThings, IGameCause cause, String remark) {
		return add(entityId, rewardThings, cause, NoticeType.NORMAL, remark);
	}
	
	public final RewardReceipt<Num> add(long entityId, List<Reward> rewardThings, IGameCause cause, NoticeType noticeType) {
		return add(entityId, rewardThings, cause, noticeType, "");
	}

	public final RewardReceipt<Num> add(long entityId, Reward rewardThing, IGameCause cause, NoticeType noticeType) {
		return add(entityId, Collections.singletonList(rewardThing), cause, noticeType, "");
	}
	
	public final RewardReceipt<Num> add(long entityId, Reward rewardThing, IGameCause cause, NoticeType noticeType, String remark) {
		return add(entityId, Collections.singletonList(rewardThing), cause, noticeType, remark);
	}

	public final RewardReceipt<Num> add(long entityId, Reward rewardThing, IGameCause cause, String remark) {
		return add(entityId, rewardThing, cause, NoticeType.NORMAL, remark);
	}
	
	public final RewardReceipt<Num> add(long entityId, Reward rewardThing, IGameCause cause) {
		return add(entityId, rewardThing, cause, NoticeType.NORMAL, "");
	}
	
	/**
	 * 根据物品类型对物品进行分组
	 * @param things
	 * @return
	 */
	private final <T extends IThing> Map<ThingStorer<Num, Reward, Cost, RReceipt, CReceipt>, List<T>> groupingThingByType(List<T> things) {
		Map<ThingStorer<Num, Reward, Cost, RReceipt, CReceipt>, List<T>> map = new LinkedHashMap<>();
		for(T thing : things) {
			IThingType thingType = thingTypeOf(thing);
			ThingStorer<Num, Reward, Cost, RReceipt, CReceipt> storer = this.getStorer(thingType);
			List<T> groupList = map.get(storer);
			if(groupList == null) {
				groupList = new ArrayList<>();
				map.put(storer, groupList);
			}
			groupList.add(thing);
		}
		return map;
	}
	
	/**
	 * 获取物品持有者的数据仓库分区
	 * @param entityId
	 * @return
	 */
	public abstract int dbZone(long entityId);
	
	protected abstract List<Reward> mergeRewards(List<Reward> rewardThings);
	protected abstract void addFinished(long entityId, RewardReceipt<Num> rewardReceipt, NoticeType noticeType, String remark);
	
	protected abstract List<Cost> mergeCosts(List<Cost> costThings);
	protected abstract void costFinished(long entityId, CostReceipt<Num> costReceipt, String remark);
}
